﻿using Microsoft.TeamFoundation.VersionControl.Client;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml;

namespace CodeMetricsCheckInPolicy
{
    [Serializable]
    public class CodeMetricsCheckInPolicyClass : PolicyBase
    {

        public override string Description
        {
            get { return "Check-in Policy to ensure that code adheres to Code Metrics values"; }
        }

        public override bool Edit(IPolicyEditArgs policyEditArgs)
        {
            Form1 frm = new Form1();
            frm.ShowDialog();
            frm.Close();
            return true;
        }

        public override string InstallationInstructions
        {
            get { return "Download and run installer for this Check-in Policy from:"; }
        }

        public override string Type
        {
            get { return "Code Metrics CheckIn Policy"; }
        }

        public override string TypeDescription
        {
            get { return "Code Metrics Check-in Policy Class"; }
        }

        public override void Activate(PolicyFailure failure)
        {
            MessageBox.Show("Code Metrics Check-in Policy Failed", "Check-in Policy Failure");
        }

        public override void DisplayHelp(PolicyFailure failure)
        {
            MessageBox.Show(failure.Message, "Code Metrics Policy Failed");
        }

        public override PolicyFailure[] Evaluate()
        {
            //Get all pending changes that are being checked in.
            PendingChange[] allPendingChanges = PendingCheckin.PendingChanges.CheckedPendingChanges;
            string projFile = "";
            string[] files = null;
            string path = "", dir = "";
            ArrayList PolicyFailures = new ArrayList();
            bool ProjectExists = false;
            List<string> Binaries = new List<string>();
            string config = "", platform = "", outputType = "", assemblyName = "", mode = "";
            //Go through all pending changes that are being checked in
            foreach (PendingChange change in allPendingChanges)
            {
                //Set value of path variable to the local path of the item that we are inspecting
                path = change.LocalItem;
                if (path.EndsWith(".cs") || path.EndsWith(".vb")) //We are interested only if code files are being checked in
                {
                    //Get the parent folder of that items folder and store it in variable dir
                    dir = path.Remove(path.LastIndexOf('\\'));
                    //Get all the fils from that folder
                    files = Directory.GetFiles(dir);
                    ProjectExists = false;
                    //Go through all the files to get the  Project File
                    foreach (string filePath in files)
                    {
                        // If you get the  Project file then break from inner foreach loop
                        if (filePath.Contains(".csproj") || filePath.Contains(".vbproj"))
                        {
                            projFile = filePath;
                            ProjectExists = true;
                            break;
                        }
                    }                    
                }
                if (!ProjectExists) //This will be when there is other than code file that is being checked in
                {
                    continue; //We do not want to play if it is say a .config file
                }
                else
                {
                    //Now let us read the  Project File

                    //Create a XmlDocument variable to hold the entire  Project File structure
                    XmlDocument doc = new XmlDocument();
                    //Load the  Project File structure in that XmlDocument variable
                    doc.Load(projFile);
                    //Create a Nodelist of all the elements that are part of the  Project File
                    XmlNodeList allNodeList = doc.DocumentElement.SelectNodes("//*");
                    //Check all nodes in that nodelist to get the nodes named Configuration, Platform, OutputType and AssemblyName 
                    foreach (XmlNode node in allNodeList)
                    {
                        if (node.Name == "Configuration")
                        {
                            config = node.InnerText;
                        }
                        if (node.Name == "Platform")
                        {
                            platform = node.InnerText;
                        }
                        if (node.Name == "OutputType")
                        {
                            outputType = node.InnerText;
                        }
                        if (node.Name == "AssemblyName")
                        {
                            assemblyName = node.InnerText;
                        }
                    }
                    mode = config + "|" + platform;
                    //Add the appropriate extension to the assembly name
                    if (outputType == "Library")
                    {
                        assemblyName += ".dll";
                    }
                    else
                    {
                        assemblyName += ".exe";
                    }
                    //Now we want to get all the children of PropertyGroup node that is having the value of mode 
                    XmlNodeList Children = null;
                    //Create a condition to check for each node
                    string condition = "$(Configuration)|$(Platform)' == '" + mode;
                    foreach (XmlNode node in allNodeList)
                    {
                        //If a node that matches the condition is found
                        if (node.Name == "PropertyGroup" && node.OuterXml.Contains(condition))
                        {
                            //Store all the children of that node in the NodeList named Children and then break out of foreach loop.
                            Children = node.ChildNodes;
                            break;
                        }
                    }
                    //From all the children we want a child node named OutputPath
                    string outputPath = "";
                    foreach (XmlNode child in Children)
                    {
                        //If a child node of the name OutputPath is found
                        if (child.Name == "OutputPath")
                        {
                            //Store the value of it in a string varaible named outputPath and then break out of foreach loop
                            outputPath = child.InnerText;
                            break;
                        }
                    }
                    //Now we want to find any .dll or .exe that is contained in the outputPath folder

                    //Get all the files that are in outputPath folder
                    string[] filesInOutputPath = Directory.GetFiles(dir + "\\" + outputPath);
                    //Check all those files for the condition that it has assemblyName as stored earlier
                    bool TargetAssyFound = false;
                    foreach (string file in filesInOutputPath)
                    {
                        //If a file with extension .dll or .exe is found then
                        if (file.EndsWith(assemblyName))
                        {
                            //Add it to the List named Binaries
                            Binaries.Add(file);
                            TargetAssyFound = true;
                        }
                    }
                    if (!TargetAssyFound)
                    {
                        PolicyFailures.Add("Assembly" + assemblyName + " is not built yet. Code Metrics cannot be calcualted");
                    }
                }
            }
            //Let us now execute CodeMetrics tool on all the binaries that we have found 
            //First step is to remove all duplicates
            Binaries = Binaries.Distinct().ToList();
            if (Binaries.Count == 0)
            {
                PolicyFailures.Add("Binaries are not built yet. Code Metrics cannot be calculated");
            }
            else
            {
                string resultFile = ""; //This variable stores the path of the result file created by Metrics.exe
                foreach (string BinaryFile in Binaries)
                {
                    //In this code we will call the Metrics.exe as an external program. It is part of the TFS Power Tools (http://www.microsoft.com/en-us/download/details.aspx?id=41647). 
                    //Download and install it on all the developers machines
                    ProcessStartInfo startInfo = new ProcessStartInfo();
                    startInfo.CreateNoWindow = false;
                    //startInfo.UseShellExecute = true;
                    startInfo.WindowStyle = ProcessWindowStyle.Hidden;

                    string VSPath = Environment.GetEnvironmentVariable("VS120COMNTOOLS");
                    VSPath = VSPath.Replace("\\Common7\\Tools\\", "");
                    startInfo.FileName = VSPath + @"\Team Tools\Static Analysis Tools\FxCop\metrics.exe";
                    startInfo.WindowStyle = ProcessWindowStyle.Normal;
                    resultFile = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\MetricsResults.xml";
                    startInfo.Arguments = "/f:\"" + BinaryFile + "\" /o:" + resultFile + " /gac";

                    try
                    {
                        // Start the process with the info we specified.
                        // Call WaitForExit and then the using statement will close.
                        Process exeProcess = Process.Start(startInfo);
                        exeProcess.WaitForExit(10000);
                        if (exeProcess.HasExited == false)
                        {
                            Console.WriteLine("Timeout over");
                            exeProcess.Kill();
                        }

                    }
                    catch
                    {
                        // Log error.
                    }

                    XmlDocument resultDoc = new XmlDocument();
                    resultDoc.Load(resultFile);
                    XmlNodeList resultsList = resultDoc.DocumentElement.SelectNodes("//Metric");
                    XmlNode OverallMetricNode = resultsList[0];
                    int MetricMinLimit = int.Parse(File.ReadAllText("C:\\CustomCheckinPolicies\\MetricValue.txt"));
                    if (Int32.Parse(OverallMetricNode.Attributes["Value"].Value) < MetricMinLimit)
                    {
                        PolicyFailures.Add("Code Metrics for the Binary File " + BinaryFile + " is " + OverallMetricNode.Attributes["Value"].Value.ToString() + " which is less than limit of " + MetricMinLimit);
                    }
                }
            }
            //If there are any policy failures detected so far
            if (PolicyFailures.Count > 0)
            {
                //Add those failures to the array of PolicyFailure and return that
                PolicyFailure[] failures = new PolicyFailure[PolicyFailures.Count];
                for (int i = 0; i < PolicyFailures.Count; i++)
                {
                    failures[i] = new PolicyFailure(PolicyFailures[i].ToString(), this);
                }
                return failures;
            }
            else //If there are not policy failures detected so far
            {
                //retrun empty array of PolicyFailure
                return new PolicyFailure[0];
            }
        }
    }
}
